home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Development Tools & Languages / Dylan Related / Mindy / Mindy 1.2 - portable sources / libraries / string-ext / string-hacking.dylan < prev    next >
Encoding:
Text File  |  1995-03-15  |  15.4 KB  |  527 lines  |  [TEXT/ttxt]

  1. module: string-hacking
  2. author: Nick Kramer (nkramer@cs.cmu.edu)
  3. synopsis: Random functionality for working with strings
  4. copyright:  Copyright (C) 1994, Carnegie Mellon University.
  5.             All rights reserved.
  6. rcs-header: $Header: string-hacking.dylan,v 1.4 94/11/10 13:37:12 nkramer Exp $
  7.  
  8. //======================================================================
  9. //
  10. // Copyright (c) 1994  Carnegie Mellon University
  11. // All rights reserved.
  12. // 
  13. // Use and copying of this software and preparation of derivative
  14. // works based on this software are permitted, including commercial
  15. // use, provided that the following conditions are observed:
  16. // 
  17. // 1. This copyright notice must be retained in full on any copies
  18. //    and on appropriate parts of any derivative works.
  19. // 2. Documentation (paper or online) accompanying any system that
  20. //    incorporates this software, or any part of it, must acknowledge
  21. //    the contribution of the Gwydion Project at Carnegie Mellon
  22. //    University.
  23. // 
  24. // This software is made available "as is".  Neither the authors nor
  25. // Carnegie Mellon University make any warranty about the software,
  26. // its performance, or its conformity to any specification.
  27. // 
  28. // Bug reports, questions, comments, and suggestions should be sent by
  29. // E-mail to the Internet address "gwydion-bugs@cs.cmu.edu".
  30. //
  31. //======================================================================
  32.  
  33. // add has no useful guarenteed behavior on strings.
  34. //
  35. define method add-last (string :: <string>, character :: <character>) 
  36.     => new-string :: <string>;
  37.   concatenate(string, make(<string>, size: 1, fill: character));
  38. end method add-last;
  39.  
  40.  
  41. // Like character-- in C
  42. //
  43. define method predecessor (c :: <character>) => c2 :: <character>;
  44.   as(<character>, as(<fixed-integer>, c) - 1);
  45. end method predecessor;
  46.  
  47.  
  48. // Like character++ in C
  49. //
  50. define method successor (c :: <character>) => c2 :: <character>;
  51.   as(<character>, as(<fixed-integer>, c) + 1);
  52. end method successor;
  53.  
  54.  
  55. define method case-insensitive-equal (o1 :: <object>, o2 :: <object>)
  56.  => answer :: <boolean>;
  57.   #f;
  58. end method case-insensitive-equal;
  59.  
  60.  
  61. // This is useful for converting from uppercase to lowercase
  62. //
  63. define constant a-minus-A 
  64.   = as(<fixed-integer>, 'a') - as(<fixed-integer>, 'A');
  65.  
  66. // Only works for ASCII and Unicode, and the case folding part works
  67. // only for English.
  68. //
  69. // The idea is to do equality checks first, and only if they are
  70. // somehow equal do further computation to see if the equality
  71. // actually meant anything.
  72. //
  73. define method case-insensitive-equal (c1 :: <character>, c2 :: <character>)
  74.     => answer :: <boolean>;
  75.   c1 == c2
  76.     | (as(<fixed-integer>, c1)
  77.       == as(<fixed-integer>, c2) + a-minus-A & uppercase?(c2))
  78.     | (as(<fixed-integer>, c1) + a-minus-A
  79.      == as(<fixed-integer>, c2) & uppercase?(c1));
  80. end method case-insensitive-equal;
  81.  
  82.  
  83. define method case-insensitive-equal (s1 :: <string>, s2 :: <string>)
  84.  => answer :: <boolean>;
  85.   if (size(s1) ~= size(s2))
  86.     #f;
  87.   else
  88.     block (return)
  89.       for (c1 in s1, c2 in s2)
  90.     if (~ case-insensitive-equal(c1, c2))
  91.       return(#f);
  92.     end if;
  93.       end for;
  94.       #t;
  95.     end block;
  96.   end if;
  97. end method case-insensitive-equal;
  98.  
  99. /* ----------------------------------------------------------------- */
  100.  
  101. // The following two functions are not exported.
  102.  
  103. define method xor (value1 :: <object>, value2 :: <object>) 
  104.  => answer :: <object>;
  105.   if (value1)
  106.     ~value2;
  107.   else
  108.     value2;
  109.   end if;
  110. end method xor;
  111.  
  112. // This does a "reverse curry".  It takes a function of one argument,
  113. // and returns a function of two arguments that ignores the second
  114. // argument.  Useful for making functions for remove!.
  115. //
  116. define method make-test (predicate? :: <function>) => tester :: <function>;
  117.   method (value :: <object>, ignored :: <object>) => answer :: <boolean>;
  118.     predicate?(value);
  119.   end method;
  120. end method make-test;
  121.  
  122.  
  123. define constant <character?> = union(<character>, singleton(#f));
  124.  
  125. /* ----------------------------------------------------------------- */
  126.  
  127. // Character-set: A reasonably efficient way of storing sets of
  128. // characters.  Store byte characters in a vector of size 256, and
  129. // keep the rest as sequences of ranges and single characters.
  130. //
  131. define sealed abstract class <character-set> (<collection>)
  132.   slot byte-characters :: <byte-character-table>,
  133.     init-function: method () make(<byte-character-table>) end;
  134.   slot char-ranges :: <vector>;
  135.                // sequence of begin-char/end-char pairs
  136.   slot single-chars :: <unicode-string>;
  137.                // Characters that aren't part of a range
  138.   slot negated-set? :: <boolean>;
  139. end class <character-set>;
  140.  
  141.  
  142. // Uses = as a comparison
  143. //
  144. define class <case-sensitive-character-set> (<character-set>)
  145. end class <case-sensitive-character-set>;
  146.  
  147.  
  148. // Uses case-insensitive-equal as a comparison
  149. //
  150. define class <case-insensitive-character-set> (<character-set>)
  151. end class <case-insensitive-character-set>;
  152.  
  153.  
  154. define method key-test (set :: <case-sensitive-character-set>)
  155.  => id :: <function>;
  156.   \==;
  157. end method key-test;
  158.  
  159.  
  160. define method key-test (set :: <case-insensitive-character-set>)
  161.  => case-insensitive-equal :: <function>;
  162.   case-insensitive-equal;
  163. end method key-test;
  164.  
  165. define method class-for-copy (set :: <character-set>) => cls :: <class>;
  166.   <object-table>;
  167. end method class-for-copy;
  168.  
  169. // Fills the byte-vector with #t's corresponding to byte characters in
  170. // the ranges and single-chars.  Also converts ranges and single-chars
  171. // to vectors and strings, respectively.  negated: is handled by
  172. // an init-keyword.
  173. //
  174. define method initialize (set :: <character-set>,
  175.               #next next-method, 
  176.               #key description = "",
  177.               #all-keys)
  178.  => false :: singleton(#f);
  179.   next-method();
  180.   let (ranges, chars, negated) = parse-description(description);
  181.   set.negated-set? := negated;
  182.   if (negated)
  183.     // Add all byte characters to the vector, and we will delete the ones we
  184.     // don't want.
  185.     for (i from 0 below 256)
  186.       set.byte-characters[as(<byte-character>, i)] := #t;
  187.     end for;
  188.   end if;
  189.     
  190.   handle-single-chars!(set, chars);
  191.  
  192.   let unicode-ranges = #();
  193.   for (range in ranges)
  194.     let first = head(range);
  195.     let last = tail(range);
  196.     if (byte-character?(first) & byte-character?(last))
  197.       for (c = first then successor(c), until c > last)
  198.     add-to-byte-vector!(set, c);
  199.       end for;
  200.     else 
  201.       unicode-ranges := add!(unicode-ranges, range);
  202.     end if;
  203.   end for;
  204.   set.char-ranges := as(<vector>, unicode-ranges);
  205.   #f;
  206. end method initialize;
  207.  
  208.  
  209. // Not exported.  Turns the appropriate character or characters in the
  210. // byte-vector to #t.
  211. //
  212. define method add-to-byte-vector!
  213.     (set :: <case-sensitive-character-set>, char :: <byte-character>)
  214.  => false :: singleton(#f);
  215.   set.byte-characters[char] := ~set.negated-set?;
  216.   #f;
  217. end method add-to-byte-vector!;
  218.  
  219.  
  220. define method add-to-byte-vector!
  221.     (set :: <case-insensitive-character-set>, char :: <byte-character>)
  222.  => false :: singleton(#f);
  223.   set.byte-characters[as-lowercase(char)] := ~set.negated-set?;
  224.   set.byte-characters[as-uppercase(char)] := ~set.negated-set?;
  225.   #f;
  226. end method add-to-byte-vector!;
  227.  
  228.  
  229. define variable no-default = pair(#f, #f);
  230.  
  231. // Call member? to do real work.
  232. //
  233. define method element (set :: <character-set>, char :: <character>,
  234.                #key default = no-default)
  235.  => char-or-f :: <character?>;
  236.   if (member?(char, set))
  237.     char;
  238.   elseif (default == no-default)
  239.     error("Element %= not found", char);
  240.   else
  241.     default;
  242.   end if;
  243. end method element;
  244.  
  245.  
  246. // test: is accepted but ignored.
  247. //
  248. define method member? (char :: <byte-character>, set :: <character-set>, 
  249.                #next next-method, #key test)
  250.  => answer :: <boolean>;
  251.   if (~test | test == key-test(set))
  252.     in-byte-vector?(set, char);
  253.   else
  254.     next-method();
  255.   end if;
  256. end method member?;
  257.  
  258.  
  259. // char is not a byte-character
  260. //
  261. define method member? (c :: <character>, set :: <character-set>,
  262.                #key test: ignored)
  263.  => answer :: <boolean>;
  264.   xor(in-single-chars?(c, set) | in-ranges?(c, set), set.negated-set?);
  265. end method member?;
  266.  
  267.  
  268. define method handle-single-chars! (set :: <character-set>, 
  269.                     char-coll :: <collection>)
  270.  => same-set :: <character-set>;
  271.   let not-byte-chars = make(<unicode-string>, size: 0);
  272.   for (c in char-coll)
  273.     if (instance?(c, <byte-character>))
  274.       add-to-byte-vector!(set, c);
  275.     else
  276.       not-byte-chars := add!(not-byte-chars, c);
  277.     end if;
  278.   end for;
  279.   set.single-chars := not-byte-chars;
  280.   set;
  281. end method handle-single-chars!;
  282.  
  283.  
  284. // Convert a character set string (without [ and ]) into a character set.
  285. //
  286. define method as (type == <character-set>, coll :: <collection>)
  287.  => set :: <character-set>;
  288.   error("Need to specify whether you want a <case-sensitive-character-set>"
  289.       " or a <case-insensitive-character-set>");
  290. end method as;
  291.  
  292.  
  293. define method as
  294.     (type == <case-sensitive-character-set>, coll :: <collection>)
  295.  => set :: <character-set>;
  296.   let set = make(<case-sensitive-character-set>);
  297.   handle-single-chars!(set, coll);
  298.   set;
  299. end method as;
  300.  
  301.  
  302. define method as
  303.     (type == <case-insensitive-character-set>, coll :: <collection>)
  304.  => set :: <character-set>;
  305.   let set = make(<case-insensitive-character-set>);
  306.   handle-single-chars!(set, coll);
  307.   set;
  308. end method as;
  309.  
  310.  
  311. // Not exported.
  312. // Type is either <case-sensitive..> or <case-insensitive...>
  313. //
  314. define method parse-description (string :: <sequence>);
  315.   let s = make(<parse-string>, string: string);
  316.   let negated = (lookahead(s) == '^');
  317.   if (negated)   consume(s)   end;
  318.  
  319.   let char-list  = #();
  320.   let range-list = #();
  321.  
  322.   until (lookahead(s) = #f)         // until end of string
  323.     let char = lookahead(s);
  324.     consume(s);
  325.     if (lookahead(s) = '-')
  326.       consume(s);
  327.       let second-char = lookahead(s);
  328.       consume(s);
  329.       range-list := add!(range-list, pair(char, second-char));
  330.     elseif (char = '\\')
  331.       let escaped-char = lookahead(s);
  332.       consume(s);
  333.       select (escaped-char by \=)
  334.     'n' => char-list  := add!(char-list, '\n');    // newline
  335.     't' => char-list  := add!(char-list, '\t');    // tab
  336.     'f' => char-list  := add!(char-list, '\f');    // formfeed
  337.     'r' => char-list  := add!(char-list, '\r');    // carriage return
  338.     'b' => char-list  := add!(char-list, '\b');    // backspace
  339.  
  340.     'd' => range-list := add!(range-list, pair('0', '9'));  // digit-char
  341.  
  342.     'w' =>                                              // word-char
  343.       range-list := concatenate(range-list, list(pair('a', 'z'), 
  344.                              pair('A', 'Z'), 
  345.                              pair('0', '9')));
  346.       char-list := add!(char-list, '_');
  347.  
  348.     's' => char-list := concatenate(char-list, " \t\n\r\f");  // whitespace
  349.     otherwise => char-list := add!(char-list, escaped-char);
  350.       end select;
  351.     else
  352.       char-list := add!(char-list, char);
  353.     end if;
  354.   end until;
  355.   values(range-list, char-list, negated);
  356. end method parse-description;
  357.  
  358.  
  359. // Not highly useful for a non-mutable class, but why bother erasing
  360. // perfectly good code..
  361. //
  362. define method shallow-copy (set :: <character-set>) 
  363.  => new-set :: <character-set>;
  364.   let new-set = make(object-class(set));
  365.   for (elt keyed-by key in set.byte-characters)
  366.     new-set.byte-characters[key] := elt;
  367.   end for;
  368.   new-set.char-ranges := shallow-copy(set.char-ranges);
  369.   new-set.single-chars := shallow-copy(set.single-chars);
  370.   new-set.negated-set? := set.negated-set?;
  371.   new-set;
  372. end method shallow-copy;
  373.  
  374.  
  375. // The following in-? functions are not exported, and ignore the
  376. // negated? bit.
  377. //
  378. define method in-byte-vector? (set :: <character-set>, c :: <byte-character>)
  379.  => answer :: <boolean>;
  380.   set.byte-characters[c];
  381. end method in-byte-vector?;
  382.  
  383.  
  384. define method in-ranges? (set :: <case-sensitive-character-set>, 
  385.               c :: <character>)
  386.  => answer :: <boolean>;
  387.   block (return)
  388.     for (range in set.char-ranges)
  389.       if (c >= head(range) & c <= tail(range))
  390.     return(#t);
  391.       end if;
  392.     end for;
  393.     #f;
  394.   end block;
  395. end method in-ranges?;
  396.  
  397.  
  398. define method in-ranges? (set :: <case-insensitive-character-set>, 
  399.               c :: <character>)
  400.  => answer :: <boolean>;
  401.   block (return)
  402.     for (range in set.char-ranges)
  403.       if (as-lowercase(c) >= head(range)
  404.         & as-lowercase(c) <= tail(range))
  405.     return(#t);
  406.       elseif (as-uppercase(c) >= head(range)
  407.         & as-uppercase(c) <= tail(range))
  408.     return(#t);
  409.       end if;
  410.     end for;
  411.     #f;
  412.   end block;  
  413. end method in-ranges?;
  414.  
  415.  
  416. define method in-single-chars? (set :: <case-sensitive-character-set>, 
  417.                 c :: <character>)
  418.  => answer :: <boolean>;
  419.   member?(c, set.single-chars, test: \==);
  420. end method in-single-chars?;
  421.  
  422.  
  423. define method in-single-chars? (set :: <case-insensitive-character-set>, 
  424.                 c :: <character>)
  425.  => answer :: <boolean>;
  426.   member?(c, set.single-chars, test: case-insensitive-equal);
  427. end method in-single-chars?;
  428.  
  429.  
  430. define constant *max-character* = as(<character>, 65535);
  431.  
  432. // Plows through all possible characters, using member? to see if it's
  433. // a valid key.
  434. //
  435. define method forward-iteration-protocol (set :: <character-set>)
  436.   let limit
  437.     = block (found-limit) 
  438.     for (c = *max-character* then predecessor(c),
  439.          until c == as(<character>, 0))
  440.       if (member?(c, set))
  441.         found-limit(c);
  442.       end if;
  443.     finally   // c is \000
  444.       if (member?(c, set))  c  else  #f  end;
  445.     end for;
  446.       end block;
  447.  
  448.   values(as(<character>, 0),    // init
  449.      limit,                 // limit
  450.  
  451.      // next
  452.      method (set == set, state :: <character>)
  453.       => next-state :: <character>;
  454.        for (c = state then successor(c), until member?(c, set))
  455.        finally
  456.          c;
  457.        end for;
  458.      end method,
  459.      
  460.      // finished?
  461.      method (set == set, state :: <character>, limit :: <character?>)
  462.       => answer :: <boolean>;
  463.        ~limit | state == limit;
  464.      end method,
  465.  
  466.      // key
  467.      method (set == set, state :: <character>) => state :: <character>;
  468.        state;
  469.      end method,
  470.      
  471.      // element
  472.      method (set == set, state :: <character>) => state :: <character>;
  473.        state;
  474.      end method,
  475.  
  476.      // element-setter
  477.      method (value, set == set, state :: <character>) 
  478.       => state :: <character>;
  479.        error("Character sets are immutable");
  480.      end method,
  481.  
  482.      // copy-state
  483.      method (set == set, state :: <character>) => state :: <character>;
  484.        state;
  485.      end method);
  486. end method forward-iteration-protocol;
  487.  
  488. /* ----------------------------------------------------------------- */
  489.  
  490. // <byte-character-table> has nothing to do with a hashtable
  491. // (<table>). It's really just a vector that uses byte-characters instead
  492. // of integers as indices.
  493. //
  494. define class <byte-character-table> (<mutable-explicit-key-collection>)
  495.   slot jump-vector :: <simple-object-vector>, 
  496.     init-function: method () 
  497.              make(<simple-object-vector>, size: 256, fill: #f) 
  498.            end;
  499. end class <byte-character-table>;
  500.  
  501.  
  502. // This function doesn't believe in the concept of defaults.
  503. // The parameter is there only to make the compiler happy.
  504. //
  505. define method element (jt :: <byte-character-table>, key :: <character>,
  506.                #key default: default = #f)
  507.   jt.jump-vector [as(<fixed-integer>, key)];
  508. end method element;
  509.  
  510.  
  511. define method element-setter (value, jt :: <byte-character-table>, 
  512.                   key :: <character>) => value;
  513.   jt.jump-vector [as(<fixed-integer>, key)] := value;
  514. end method element-setter;
  515.   
  516.  
  517. define method forward-iteration-protocol (jt :: <byte-character-table>);
  518.   values(0, 256,        // init and limit
  519.      method (coll, state) state + 1 end,               // next-state
  520.      method (coll, state, limit) state >= limit end,   // finished-state?
  521.      method (coll, state) as(<character>, state) end,  // current-key
  522.      method (coll, state) jt.jump-vector[state] end,   // current-elt
  523.      method (value, coll, state) jt.jump-vector[state] := value end,
  524.                     // Current-elt-setter
  525.      method (coll, state) state end);                  // copy-state
  526. end method forward-iteration-protocol;
  527.